﻿/*
____
 L|ypps Web controls for ASP.NET

Copyright (c) 2009 Alessandro Zifiglio, http://www.Typps.com
EMail: alessandro@Typps.com

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
namespace Typps
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.Configuration;
    using System.IO;
    using System.Text;
    using System.Text.RegularExpressions;
    using System.Web;
    using System.Web.UI;
    using System.Xml;

    using Sgml;

    /// <summary>
    /// Summary description for MSWordParser
    /// </summary>
    public class MSWordParser
    {
        #region Fields

        const string msoClass = "class=Mso";
        const string msoStyleAttribute = "mso-"; //found inside style="mso-ansi-language: NO-BOK;"
        const string xmlEndTagFormat = "</{0}:";
        const string xmlNameSpace = "<?xml:namespace"; // prefix = st1 ns = \"urn:schemas-microsoft-com:office:smarttags\" />";
        const string xmlStartTagFormat = "<{0}:";

        List<string> css2Attributes = new List<string>();
        string[] cssProperties = null;
        Dictionary<string, string> styleClasses = null;
        List<string> xmlPrefixList = new List<string>();
        Dictionary<string, string> _attributesToReplace;
        Dictionary<string, string> _elementsToReplace;
        private StringWriter _errorLogValue;
        Dictionary<string, StripMode> _fullElementsToStrip = null;
        List<string> _msoClassNames = null;
        bool _removeLangAttribute = true;

        #endregion Fields

        #region Constructors

        public MSWordParser()
            : this(null, null)
        {
        }

        public MSWordParser(Dictionary<string,string> attributes, Dictionary<string,string> elements, bool removeLangAttribute)
            : this(attributes,elements)
        {
            _removeLangAttribute = removeLangAttribute;
        }

        public MSWordParser(Dictionary<string, string> attributes, Dictionary<string, string> elements)
        {
            if (attributes != null)
                _attributesToReplace = attributes;
            if (elements != null)
                _elementsToReplace = elements;

            FillCSSAttributes();
            //default attributes
            AttributesToReplace.Add("face", "font-family");
            AttributesToReplace.Add("color", "color");
            AttributesToReplace.Add("size", "font-size");
            AttributesToReplace.Add("v:shapes", string.Empty);
            AttributesToReplace.Add("height", "height");
            AttributesToReplace.Add("width", "width");
            AttributesToReplace.Add("hspace", string.Empty);
            AttributesToReplace.Add("vspace", string.Empty);
            AttributesToReplace.Add("class", string.Empty);
            if(RemoveLangAttribute)
                AttributesToReplace.Add("lang", string.Empty);
            //default elements
            ElementsToReplace.Add("<?xml:namespace", string.Empty);
            ElementsToReplace.Add("<meta", string.Empty);
            ElementsToReplace.Add("<font", "<span");
            ElementsToReplace.Add("</font>", "</span>");
            /*<u> element is deprecated, but let's make an exception for now!*/
            //ElementsToReplace.Add("<u","<span style=\"text-decoration:underline\"");
            //ElementsToReplace.Add("</u>", "</span>");

            //entire elements to strip
            FullElementsToStrip.Add("w:", StripMode.TagAndContent);
            FullElementsToStrip.Add("o:", StripMode.TagAndContent);
            FullElementsToStrip.Add("v:", StripMode.TagAndContent);
            FullElementsToStrip.Add("x:", StripMode.TagAndContent);
            FullElementsToStrip.Add("p:", StripMode.TagAndContent);
            FullElementsToStrip.Add("style", StripMode.TagAndContent);
            FullElementsToStrip.Add("meta", StripMode.TagAndContent);
            FullElementsToStrip.Add("link", StripMode.TagAndContent);
            FullElementsToStrip.Add("<!--[if !mso", StripMode.TagAndContent);
            FullElementsToStrip.Add("<!--[if gte mso", StripMode.TagAndContent);
            FullElementsToStrip.Add("<!--[if gte vml", StripMode.TagAndContent);
            if (RemoveLangAttribute)
            {
                FullElementsToStrip.Add("span style=\"mso-ansi-language:", StripMode.TagOnly);
                FullElementsToStrip.Add("span style=\"\" lang=", StripMode.TagOnly);
                FullElementsToStrip.Add("span lang=", StripMode.TagOnly);
            }
            FullElementsToStrip.Add("span style=\"mso-spacerun:", StripMode.TagOnly);
            MsoClassNames.Add("MsoNormal");
            MsoClassNames.Add("MsoTable");
            MsoClassNames.Add("MsoChp");
            MsoClassNames.Add("MsoPap");
            MsoClassNames.Add("WordSection1");
            MsoClassNames.Add("Heading1Char");
        }

        #endregion Constructors

        #region Properties

        /// <summary>
        /// Add  your list of attributes you'd like to replace here in the form of start tag and replacement start tag, and end tag and replacement for end tag eg :
        /// AttributesToReplace.Add("<font","<span");
        /// AttributesToReplace.Add("</font","</span");
        /// </summary>
        public Dictionary<string, string> ElementsToReplace
        {
            get
            {
                if (_elementsToReplace == null)
                    _elementsToReplace = new Dictionary<string, string>();
                return _elementsToReplace;
            }
        }

        public StringWriter ErrorLog
        {
            get
            {
                if (_errorLogValue == null)
                    _errorLogValue = new StringWriter();
                return _errorLogValue;
            }
            set { _errorLogValue = value; }
        }

        /// <summary>
        /// Provide your list of elements you want stripped out here eg :
        /// FullElementsToStrip.Add("meta", StripMode.TagAndContent);
        /// </summary>
        public Dictionary<string, StripMode> FullElementsToStrip
        {
            get
            {
                if (_fullElementsToStrip == null)
                    _fullElementsToStrip = new Dictionary<string, StripMode>();
                return _fullElementsToStrip;
            }
            set { _fullElementsToStrip = value; }
        }

        /// <summary>
        /// A list of ms office classnames. ms office likes to add useless ms specific classnames in the document. mostly these class names start with Mso prefix
        /// but not always. If you find a class just sitting in your document, it means that class is not covered by the internal list of classes to remove.
        /// So imply add your class name to this list and it will get stripped out.
        /// </summary>
        public List<string> MsoClassNames
        {
            get
            {
                if (_msoClassNames == null)
                    _msoClassNames = new List<string>();
                return _msoClassNames;
            }
            set
            {
                _msoClassNames = value;
            }
        }

        /// <summary>
        /// Removes useless lang attributes found in the document which msword likes to create in abundance. 
        /// By defalt this is true and can be overridden only in the constructor
        /// </summary>
        public bool RemoveLangAttribute
        {
            get { return _removeLangAttribute; }
        }

        /// <summary>
        /// Add  your list of attributes to replace with inline css alternatives eg
        /// AttributesToReplace.Add("face", "font-family");
        /// AttributesToReplace.Add("color", "color");
        /// </summary>
        internal Dictionary<string, string> AttributesToReplace
        {
            get
            {
                if (_attributesToReplace == null)
                    _attributesToReplace = new Dictionary<string, string>();
                return _attributesToReplace;
            }
        }

        #endregion Properties

        #region Methods

        public string Cleanup(string text)
        {
            StringBuilder output = new StringBuilder();
            List<string> htmlAttributes = new List<string>();
            List<string> styleAttributes = new List<string>();
            List<string> xmlPrefixes = new List<string>();

            string startTag = string.Empty;
            string endTag = string.Empty;
            bool appendToStartElement = false;
            bool appendToEndElement = false;
            bool skip = false;

            string lastChar = string.Empty;

            text = StripFullElement(text);
            text = RemoveConditionalNotVMLTag(text);
            text = StripIEConditionalStatements(text);
            char[] input = text.ToCharArray();
            for (int num1 = 0; num1 < input.Length; num1++)
            {
                string c1 = input[num1].ToString();
                switch (c1)
                {
                    case "<":
                        if (!appendToStartElement)
                        {
                            startTag = c1;
                            appendToStartElement = true;
                            appendToEndElement = false;
                        }
                        else if (!appendToEndElement)
                        {
                            endTag = c1;
                            appendToEndElement = true;
                            appendToStartElement = false;
                        }
                        break;
                    case ">":
                        if (appendToStartElement)
                        {
                            startTag += c1;
                            if (startTag.IndexOf('/') != -1)
                            {
                                // we have a self closing element without
                                // content, so we've reached the closing tag
                                appendToEndElement = false;
                            }
                            appendToStartElement = false;
                            ParseXmlNamespace(startTag);
                            startTag = FixDeprecatedAttributes(startTag);
                            startTag = RemoveMSWordSpecificStyleAttributes(startTag);
                            startTag = RemoveMsoClass(startTag);
                            bool flag1 = SubstituteMatchingElement(startTag, out startTag);
                            bool flag2 = false;
                            if (!flag1)
                                flag2 = IsXmlElement(startTag, TagType.opening);

                            if (!flag2)//if it's an xml tag, we dont want it
                                output.Append(startTag);
                            skip = true;
                        }
                        else if (appendToEndElement)
                        {
                            endTag += c1;
                            appendToEndElement = false;
                            bool flag3 = SubstituteMatchingElement(endTag, out endTag);
                            bool flag4 = false;
                            if (!flag3)
                                flag4 = IsXmlElement(endTag, TagType.closing);
                            if (!flag4)
                                output.Append(endTag);
                            skip = true;
                        }
                        break;
                    case "/":
                        if (lastChar == "<" &&
                            (appendToStartElement || appendToEndElement))
                        {
                            if (appendToStartElement)
                            {
                                appendToStartElement = false;
                                appendToEndElement = true;
                                endTag = startTag;
                            }
                        }
                        if (appendToEndElement)
                            endTag += c1;
                        else if (appendToStartElement)
                            startTag += c1;
                        break;
                    default:
                        if (appendToStartElement)
                        {
                            startTag += c1;
                        }
                        else if (appendToEndElement)
                        {
                            endTag += c1;
                        }
                        break;
                }
                if ((!appendToStartElement &&
                    !appendToEndElement) && !skip)
                    output.Append(c1);

                skip = false;//reset
                lastChar = c1;
            }
            return output.ToString();
        }

        /// <summary>
        /// Strips elements contained in the FullElementTopStrip list. This means
        /// the element starting and ending tags and whatever is in between.
        /// This method is also tolerant to unclosed tags and selfclosed tags.
        /// </summary>
        /// <param name="html"></param>
        /// <returns></returns>
        public string StripFullElement(string html)
        {
            foreach (KeyValuePair<string, StripMode> kvp in FullElementsToStrip)
            {
                string elem = kvp.Key;
                StripMode mode = kvp.Value;
                int startIndex = -1;
                do
                {
                    string startTagName=string.Format("<{0}", elem);
                    startIndex = html.IndexOf(startTagName, StringComparison.OrdinalIgnoreCase);
                    if (startIndex != -1)
                    {
                        int closingTagIndex = html.IndexOf("/>", startIndex);
                        if (closingTagIndex != -1)
                            closingTagIndex += "/>".Length;
                        int nextStartingTagIndex = html.IndexOf("<", startIndex + 1);
                        string endingTagName = elem;
                        string closingTag = string.Empty;
                        if (closingTagIndex == -1 || closingTagIndex > nextStartingTagIndex)
                        {
                            int attributeIndex = elem.IndexOf(' ');
                            if (attributeIndex != -1)
                            {
                                //we have whitespace (so it's not the element name but also contains attributes etc
                                endingTagName = elem.Substring(0, attributeIndex);
                            }
                            closingTag = string.Format("</{0}", endingTagName);
                            closingTagIndex = html.IndexOf(closingTag, startIndex,
                                StringComparison.OrdinalIgnoreCase);
                            if (closingTagIndex == -1)
                                closingTagIndex = html.IndexOf(">", startIndex);//no self closing tag.
                            else
                                closingTagIndex = html.IndexOf(">", closingTagIndex);
                            closingTagIndex += ">".Length;
                        }
                        int closingAngleBracketIndex = html.IndexOf(">", startIndex);
                        if (closingTagIndex != -1)
                        {
                            nextStartingTagIndex = html.IndexOf("<", startIndex + 1);

                            int length = -1;

                            if (closingAngleBracketIndex < nextStartingTagIndex ||
                                nextStartingTagIndex == -1)
                                length = closingTagIndex - startIndex;
                            else if (nextStartingTagIndex != -1)
                                length = nextStartingTagIndex - startIndex;

                            if (length > 0)
                            {
                                string replacement = string.Empty;
                                if (mode == StripMode.TagAndContent)
                                {
                                    replacement = html.Substring(startIndex, length);
                                    html = html.Remove(startIndex, length);
                                }
                                else
                                {
                                    //remove only starting tag and ending tag, but keep the content
                                    replacement = html.Substring(startIndex, (closingAngleBracketIndex - startIndex) + ">".Length);
                                    html = html.Remove(startIndex, (closingAngleBracketIndex - startIndex) + ">".Length);
                                    closingTagIndex = html.IndexOf(closingTag, startIndex);
                                    replacement += closingTag;
                                    if(closingTagIndex>-1)
                                        html = html.Remove(closingTagIndex, closingTag.Length+">".Length);
                                }
                                if (replacement.IndexOf("<style", StringComparison.OrdinalIgnoreCase) != -1 &&
                                    replacement.IndexOf("</style", StringComparison.OrdinalIgnoreCase) != -1)
                                    ParseClassNames(ref html, replacement);
                                ErrorLog.WriteLine(string.Format("### Element not allowed inside an html document body. Removing {0}.", replacement));

                            }
                            else
                            {
                                break;
                            }
                        }
                    }
                } while (startIndex != -1);
            }
            return html;
        }

        /// <summary>
        /// Strips entire conditional statements. We don't require this since it's
        /// not legal to have style or style sheet links in the body to comform
        /// with xhtml standards. Ms word in addition outputs some proprietary specific
        /// verbose css and xml that you really* want to strip.
        /// </summary>
        /// <param name="html"></param>
        /// <returns></returns>
        public string StripIEConditionalStatements(string html)
        {
            foreach (KeyValuePair<string, StripMode> kvp in FullElementsToStrip)
            {
                string elem = kvp.Key;
                StripMode mode = kvp.Value;
                if (elem.IndexOf("<!--[if", StringComparison.OrdinalIgnoreCase) == -1)
                    continue;
                int startIndex = -1;
                do
                {
                    startIndex = html.IndexOf(elem, StringComparison.OrdinalIgnoreCase);
                    if (startIndex != -1)
                    {
                        //<![endif]--> downlevel closing tag
                        //<!--[endif]--> uplevel closing tag
                        int closingTagLength = "<![endif]-->".Length;
                        int closingTagIndex = html.IndexOf("<![endif]-->", startIndex,
                            StringComparison.OrdinalIgnoreCase);

                        int closingUpLevelTagIndex = html.IndexOf("<!--[endif]-->", startIndex,
                            StringComparison.OrdinalIgnoreCase);

                        if (closingUpLevelTagIndex != -1 && (closingTagIndex > closingUpLevelTagIndex || closingTagIndex == -1))
                        {
                            closingTagIndex = closingUpLevelTagIndex;
                            closingTagLength = "<!--[endif]-->".Length;
                        }
                        if (closingTagIndex != -1)
                        {
                            int length = (closingTagIndex + closingTagLength) - startIndex;
                            if (length > 0)
                            {
                                string replacement = html.Substring(startIndex, length);
                                ErrorLog.WriteLine(string.Format("### Found ms-word specific conditional statements embedded in the body typically used for containing styles by ms-word generated HTML. Removing {0} since style containment in the body is not allowed.", replacement));
                                html = html.Replace(replacement, string.Empty);
                            }
                            else
                            {
                                break;
                            }
                        }
                        else
                        {
                            //no closing tag? sad! be a hero and save the situation
                            //"<!--[if gte mso 10]><p class=\"MsoNormal\">hello?</p>"
                            closingTagIndex = html.IndexOf(">", startIndex + elem.Length) + 1;
                            int nextStartingTag = html.IndexOf("<", startIndex + elem.Length);
                            if (closingTagIndex != -1)
                            {
                                if (closingTagIndex > nextStartingTag)
                                    closingTagIndex = nextStartingTag;
                                if (closingTagIndex != -1)
                                    html = html.Remove(startIndex, closingTagIndex);
                            }
                        }
                    }
                } while (startIndex != -1);
            }
            return html;
        }

        void FillCSSAttributes()
        {
            //all css1, css2 and css3 properties
            cssProperties = GetCssPropertiesCSV().Split(',');
        }

        /// <summary>
        /// We are not tolerant to duplicate attribute. This means if there is a width attribute defined, and
        /// in the style attribute there is a width attribute in css defined, this method will move the attribute
        /// width into the inline style attribute without checking if a width attribute already exists in css first.
        /// We don't want to perform a lot of processing. There is a lot going on in this method as it is.
        /// </summary>
        /// <param name="htmlElement"></param>
        /// <returns></returns>
        private string FixDeprecatedAttributes(string htmlElement)
        {
            string encodedElement = HtmlDecodeQuotedAttributeValue(htmlElement);
            string[] attributes = encodedElement.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries);
            bool isEmptyStyleAttribute;

            string originalStyleAttributes = GetStyleAttributeAndValue(attributes, out isEmptyStyleAttribute);
            string decodedOriginalStyleAttributes = HttpUtility.UrlDecode(originalStyleAttributes);

            StringBuilder styleAttribute = new StringBuilder(decodedOriginalStyleAttributes);
            string message = "### Found deprecated attribute in document: {0}, replacing with standards compliant html/css";
            bool found = false;
            for (int num1 = 0; num1 < attributes.Length; num1++)
            {
                string[] attributePair = attributes[num1].Split(new char[] { '=' }, StringSplitOptions.RemoveEmptyEntries);
                if (attributePair.Length > 1)
                {
                    foreach (KeyValuePair<string, string> kvp in AttributesToReplace)
                    {
                        if (attributePair[0].Equals(kvp.Key,
                            StringComparison.OrdinalIgnoreCase))
                        {
                            if (!found)
                                found = true;

                            string attributeKey = attributePair[0].ToLower();
                            if (attributePair.Length > 1 && attributePair[1].IndexOf(">") != -1)
                                attributePair[1] = attributePair[1].Substring(0, attributePair[1].Length - 1);

                            string attributeValue = (attributePair.Length > 1) ? GetDataBetweenQuotes(attributePair[1].ToLower()) : string.Empty;
                            string oldAttribute = string.Format(" {0}={1}", attributeKey, attributePair[1]);

                            attributeValue = GetDataBetweenQuotes(attributeValue);

                            if (!isEmptyStyleAttribute && !decodedOriginalStyleAttributes.EndsWith(";"))
                                styleAttribute.Append(";");

                            bool replaceAttribute = false;
                            switch (attributeKey)
                            {
                                case "color":
                                    replaceAttribute = true;
                                    styleAttribute.AppendFormat("{0}:{1};", kvp.Value, attributeValue);
                                    break;
                                case "size":
                                    replaceAttribute = true;
                                    styleAttribute.AppendFormat("{0}:{1};", kvp.Value, GetCssSize(attributeValue));
                                    break;
                                case "face":
                                    replaceAttribute = true;
                                    styleAttribute.AppendFormat(string.Format("{0}:{1};", kvp.Value, attributeValue));
                                    break;
                                case "v:shapes":
                                    replaceAttribute = true;
                                    break;
                                case "vspace":
                                    replaceAttribute = true;
                                    styleAttribute.AppendFormat("padding-top:{0};padding-bottom:{0};", UnitParse(attributeValue));
                                    break;
                                case "hspace":
                                    replaceAttribute = true;
                                    styleAttribute.AppendFormat("padding-left:{0};padding-right:{0};", UnitParse(attributeValue));
                                    break;
                                case "width":
                                case "height":
                                    replaceAttribute = true;
                                    styleAttribute.AppendFormat("{0}:{1};", kvp.Value, UnitParse(attributeValue));
                                    break;
                                case "lang":
                                    replaceAttribute = true;
                                    break;
                                case "class":
                                    if (attributeValue.Equals("Apple-style-span",
                                        StringComparison.OrdinalIgnoreCase))
                                        replaceAttribute = true;
                                        break;
                            }
                            if (replaceAttribute)
                            {
                                ErrorLog.WriteLine(string.Format(message, oldAttribute));
                                encodedElement = Regex.Replace(encodedElement, Regex.Escape(oldAttribute),
                                    string.Empty, RegexOptions.IgnoreCase);
                            }
                        }
                    }
                }
            }
            if (found)
            {
                if (encodedElement.IndexOf(originalStyleAttributes,
                    StringComparison.OrdinalIgnoreCase) != -1)
                {
                    //appending to existing style
                    encodedElement = encodedElement.Trim().Replace(originalStyleAttributes, styleAttribute.ToString());
                }
                else
                {
                    styleAttribute.Append("\"");//brand new style
                    encodedElement = encodedElement.Trim().Insert(encodedElement.Length - 1, styleAttribute.ToString());
                }
            }
            return HttpUtility.UrlDecode(encodedElement);
        }

        string GetCssPropertiesCSV()
        {
            return @"alignment-adjust,alignment-baseline,animation,animation-delay,animation-direction,animation-duration,animation-iteration-count,animation-name,animation-play-state,animation-timing-function,appearance,azimuth,background,background-attachment,background-break,background-clip,background-color,background-image,background-origin,background-position,background-repeat,background-size,baseline-shift,binding,bookmark-label,bookmark-level,bookmark-target,border,border-bottom,border-bottom-color,border-bottom-left-radius,border-bottom-right-radius,border-bottom-style,border-bottom-width,border-collapse,border-color,border-image,border-image-outset,border-image-repeat,border-image-slice,border-image-source,border-image-width,border-left,border-left-color,border-left-style,border-left-width,border-length,border-radius,border-right,border-right-color,border-right-style,border-right-width,border-spacing,border-style,border-top,border-top-color,border-top-left-radius,border-top-right-radius,border-top-style,border-top-width,border-width,bottom,box-align,box-decoration-break,box-direction,box-flex,box-flex-group,box-lines,box-ordinal-group,box-orient,box-pack,box-shadow,box-sizing,caption-side,clear,clip,color,color-profile,column-break-after,column-break-before,column-count,column-fill,column-gap,column-rule,column-rule-color,column-rule-style,column-rule-width,column-span,column-width,columns,content,counter-increment,counter-reset,crop,cue,cue-after,cue-before,cursor,direction,display,display-model,display-role,dominant-baseline,drop-initial-after-adjust,drop-initial-after-align,drop-initial-before-adjust,drop-initial-before-align,drop-initial-size,drop-initial-value,elevation,empty-cells,fit,fit-position,float,float-offset,font,font-family,font-size,font-size-adjust,font-stretch,font-style,font-variant,font-weight,grid-columns,grid-rows,hanging-punctuation,height,hyphenate-after,hyphenate-before,hyphenate-character,hyphenate-lines,hyphenate-resource,hyphens,icon,image-orientation,image-resolution,inline-box-align,left,letter-spacing,line-height,line-stacking,line-stacking-ruby,line-stacking-shift,line-stacking-strategy,list-style,list-style-image,list-style-position,list-style-type,margin,margin-bottom,margin-left,margin-right,margin-top,mark,mark-after,mark-before,marks,marquee-direction,marquee-play-count,marquee-speed,marquee-style,max-height,max-width,min-height,min-width,move-to,nav-down,nav-index,nav-left,nav-right,nav-up,opacity,orphans,outline,outline-color,outline-offset,outline-style,outline-width,overflow,overflow-style,overflow-x,overflow-y,padding,padding-bottom,padding-left,padding-right,padding-top,page,page-break-after,page-break-before,page-break-inside,page-policy,pause,pause-after,pause-before,phonemes,pitch,pitch-range,play-during,position,presentation-level,punctuation-trim,quotes,rendering-intent,resize,rest,rest-after,rest-before,richness,right,rotation,rotation-point,ruby-align,ruby-overhang,ruby-position,ruby-span,size,speak,speak-header,speak-numeral,speak-punctuation,speech-rate,stress,string-set,tab-side,table-layout,target,target-name,target-new,target-position,text-align,text-align-last,text-decoration,text-emphasis,text-height,text-indent,text-justify,text-outline,text-replace,text-shadow,text-transform,text-wrap,top,transition,transition-delay,transition-duration,transition-property,transition-timing-function,unicode-bidi,vertical-align,visibility,voice-balance,voice-duration,voice-family,voice-pitch,voice-pitch-range,voice-rate,voice-stress,voice-volume,volume,white-space,white-space-collapse,widows,width,word-break,word-spacing,word-wrap,z-index";
        }

        private string GetCssSize(string size)
        {
            string validSize = string.Empty;
            switch (size)
            {
                case "1":
                    validSize = "xx-small";
                    break;
                case "2":
                    validSize = "x-small";
                    break;
                case "3":
                    validSize = "small";
                    break;
                case "4":
                    validSize = "smaller";
                    break;
                case "5":
                    validSize = "medium";
                    break;
                case "6":
                    validSize = "larger";
                    break;
                case "7":
                    validSize = "large";
                    break;
            }
            return validSize;
        }

        private string GetDataBetweenQuotes(string quotedText)
        {
            int index = quotedText.IndexOf('"');
            if (index != -1)
            {
                int end = quotedText.IndexOf('"', index + 1);
                if (end != -1)
                    return quotedText.Substring(index + 1, (end - 1) - index);
            }
            return quotedText;
        }

        private List<string> GetQuotedValues(string text)
        {
            List<string> quotedAttributes = new List<string>();
            int index = text.IndexOf('"');
            while (index != -1 && index < text.Length)
            {
                int end = text.IndexOf('"', index + 1);
                if (end != -1)
                {
                    string quotedWord = text.Substring(index + 1, (end - 1) - index);
                    if (!quotedAttributes.Contains(quotedWord))
                        quotedAttributes.Add(quotedWord);
                    index = text.IndexOf('"', end + 1);
                }
                else
                { break; }
            }
            return quotedAttributes;
        }

        private string GetStyleAttributeAndValue(string[] attributes, out bool isEmptyStyleAttribute)
        {
            isEmptyStyleAttribute = true;
            string styleAttribute = " style=\"";
            for (int num1 = 0; num1 < attributes.Length; num1++)
            {
                string[] attributePair = attributes[num1].Split(new char[] { '=' }, StringSplitOptions.RemoveEmptyEntries);
                if (attributePair.Length > 1)
                {
                    int index = attributePair[0].IndexOf("style",
                            StringComparison.OrdinalIgnoreCase);
                    if (index != -1)
                    {
                        styleAttribute = string.Format("style=\"{0}", GetDataBetweenQuotes(attributePair[1]));
                        isEmptyStyleAttribute = false;
                        return styleAttribute;
                    }
                }
            }
            return styleAttribute;
        }

        private string HtmlDecodeQuotedAttributeValue(string htmlElement)
        {
            List<string> quotedAttributes = GetQuotedValues(htmlElement);
            foreach (string s in quotedAttributes)
            {
                htmlElement = Regex.Replace(htmlElement, Regex.Escape(s),
                    HttpUtility.UrlEncode(s), RegexOptions.IgnoreCase);
            }
            return htmlElement;
        }

        bool IsValidCSS(string value)
        {
            foreach (string s in cssProperties)
            {
                if (s.Equals(value.Trim(), StringComparison.OrdinalIgnoreCase))
                    return true;
            }
            return false;
        }

        bool IsXmlElement(string match, TagType tag)
        {
            foreach (string s in xmlPrefixList)
            {
                if (match.IndexOf(string.Format(xmlStartTagFormat, s),
                    StringComparison.OrdinalIgnoreCase) != -1 ||
                    match.IndexOf(string.Format(xmlEndTagFormat, s),
                    StringComparison.OrdinalIgnoreCase) != -1)
                {
                    ErrorLog.WriteLine(string.Format("### Found illegal xml element in document. Removing {0} element {1}|", tag.ToString(), match));
                    return true;
                }
            }
            return false;
        }

        void ParseClassNames(ref string html, string styleElement)
        {
            if (styleClasses == null)
                styleClasses = new Dictionary<string, string>();
            int styleStartTag = styleElement.IndexOf(">") + 1;
            int styleEndTag = styleElement.IndexOf("</style>");
            string content = styleElement.Substring(styleStartTag, styleEndTag - styleStartTag);
            int classStart = content.IndexOf('.');
            if (classStart == -1) return;
            int braceStart = content.IndexOf('{', classStart) + 1;
            int braceEnd = content.IndexOf('}', braceStart);
            if (braceStart == -1 || braceEnd == -1) return;
            string className = content.Substring(classStart, braceStart - 1 - classStart);
            List<string> styles = new List<string>();
            do
            {
                string css = content.Substring(braceStart, braceEnd - braceStart);
                string[] pairs = css.Split(';');
                foreach (string p in pairs)
                {
                    string[] keyValue = p.Split(':');
                    if (keyValue.Length == 2)
                    {
                        string key = keyValue[0].Trim();
                        string value = keyValue[1].Trim().Replace("\"","'");
                        if (IsValidCSS(key))
                            styles.Add(string.Format("{0}:{1}", key, value));
                    }
                }
                braceStart = content.IndexOf('{', braceEnd);
                if (braceStart != -1)
                    braceEnd = content.IndexOf('}', braceStart);
            }
            while (braceStart != -1 && braceEnd != -1);
            styleClasses.Add(className, string.Join(";", styles.ToArray()));
        }

        /// <summary>
        /// Word documents normally mix xml markup in the html. We dont want these
        /// custom xml elements because they mean nothing on the web and do not
        /// adhere to any standards. This method removes that useless namespace.
        /// An example of this namespace with an xml element declared inline looks 
        /// like this :
        /// <?xml:namespace prefix = st1 ns = "urn:schemas-microsoft-com:office:smarttags" />
        /// or <?XML:NAMESPACE PREFIX = O />
        /// <st1:City w:st="on"><st1:place w:st="on">AJAX</st1:place></st1:City>
        /// </summary>
        /// <param name="html"></param>
        /// <returns></returns>
        private void ParseXmlNamespace(string markup)
        {
            bool hasNameSpace = markup.IndexOf("<?xml:", StringComparison.OrdinalIgnoreCase) != -1;
            if (hasNameSpace)
            {
                ErrorLog.WriteLine(string.Format("### Found illegal xml namespace declaration in document. Removing namespace {0}|", markup));
                int prefixIndex = markup.IndexOf("prefix = ", StringComparison.OrdinalIgnoreCase);
                if (prefixIndex != -1)
                {
                    int start = prefixIndex + "prefix = ".Length;
                    int end = markup.IndexOf(" ", start);

                    string prefix = string.Empty;
                    if (end != -1)
                        prefix = markup.Substring(start, end - start);
                    if (prefix != string.Empty)
                        xmlPrefixList.Add(GetDataBetweenQuotes(prefix));
                }
            }
        }

        /// <summary>
        /// Strips out just the conditional negated vml statement. The contents of this condition
        /// are kept in tact as this is the replacement content, typically an img element for user agents
        /// that do not support vml or do not intend to use the vml alternate. Indeed we do not, we want 
        /// the html alternative.
        /// </summary>
        /// <param name="html"></param>
        /// <returns></returns>
        string RemoveConditionalNotVMLTag(string html)
        {
            Pair p1 = new Pair("<![if !vml]>", "<![endif]>");
            Pair p2 = new Pair("<!--[if !vml]-->", "<!--[endif]-->");
            List<Pair> bucket = new List<Pair>(2);
            bucket.Add(p1);
            bucket.Add(p2);
            foreach (Pair p in bucket)
            {
                string startTag = (string)p.First;
                string endTag = (string)p.Second;
                int startIndex = -1;
                do
                {
                    startIndex = html.IndexOf(startTag, StringComparison.OrdinalIgnoreCase);
                    if (startIndex == -1) continue;
                    int endIndex = html.IndexOf(endTag, startIndex);
                    html = html.Remove(startIndex, startTag.Length);
                    html = html.Remove(endIndex - startTag.Length, endTag.Length);
                    ErrorLog.WriteLine(string.Format("### Found ms-word specific conditional statements for " +
                    "VML alternate content. Removing only the conditional " +
                    "start statement {0}, and ending tag {1} but keeping the contents." +
                    "This is the replacement for VML provided by ms-word itself.", startTag, endTag));
                } while (startIndex != -1);
            }
            return html;
        }

        string RemoveMsoClass(string element)
        {
            foreach (string className in MsoClassNames)
            {
                string attr1 = string.Format(" class=\"{0}", className);
                string attr2 = string.Format(" class={0}", className);
                int index = element.IndexOf(attr1, StringComparison.OrdinalIgnoreCase);
                if (index == -1)
                    index = element.IndexOf(attr2, StringComparison.OrdinalIgnoreCase);
                if (index != -1)
                {
                    int len = index + attr2.Length;
                    int space = element.IndexOf(" ", len);
                    int endTag = element.IndexOf(">", len);
                    // check to see if the space separator after the class=className attribute
                    // is indeed a space or an ending tag and use the lowest index as the ending marker for the class attribuate/value pair.
                    int end = ((space != -1) && space < endTag) ? space : endTag;
                    if (end > 0)
                    {
                        string text1 = element.Substring(index, end - index);
                        ErrorLog.WriteLine(string.Format("### Found ms-word specific class name in document. Removing {0} to reduce bloat.", text1));
                        element = element.Replace(text1, string.Empty);
                    }
                }
            }
            return element;
        }

        private string RemoveMSWordSpecificStyleAttributes(string htmlElement)
        {
            int index = htmlElement.IndexOf("style=\"", StringComparison.OrdinalIgnoreCase);
            if (index != -1)
            {
                int start = index + "style=\"".Length;
                int end = htmlElement.IndexOf("\"", start);
                if (end == -1) return htmlElement;
                int length = end - start;
                if (length == 0)
                {
                    string replacement = htmlElement.Substring(index - 1, (end - index) + 2);
                    return htmlElement.Replace(replacement, string.Empty);
                }
                /*linefeeds etc inside the style attribute aren't liked the dtdparser and it's wrong, so eliminate those and &quot; too are just wrong, fix those as well*/
                string styleAttribute = htmlElement.Substring(start, length).Replace("&quot;","'").Replace("\r\n",string.Empty);
                htmlElement=htmlElement.Remove(start, length);
                htmlElement=htmlElement.Insert(start, styleAttribute);
                string[] styleArray = styleAttribute.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
                for (int num1 = 0; num1 < styleArray.Length; num1++)
                {
                    string keyValue = styleArray[num1];
                    string[] pair = keyValue.Split(':');
                    string key = pair[0];
                    string value=string.Empty;
                    if(pair.Length>1)
                        value = pair[1];
                    if (!IsValidCSS(key) || value.IndexOf("mso-") != -1)
                    {
                        string styleTerminator = (num1 == styleArray.Length - 1) ? string.Empty : ";";
                        htmlElement = htmlElement.Replace(keyValue + styleTerminator, string.Empty);
                        ErrorLog.WriteLine(string.Format("### Found ms-word specific inline css style property. Removing {0}.", keyValue));
                    }
                }
				if (start < htmlElement.Length)
				{
					//remove the style all together if it is empty
					end = htmlElement.IndexOf("\"", start);
					length = end - start;
					styleAttribute = htmlElement.Substring(start, length);
					if (styleAttribute.Trim() == string.Empty)
						htmlElement = htmlElement.Remove(index - 1, " style=\"\"".Length);
				}
            }
            return htmlElement;
        }

        /*
        Deprecated.
        1) this is useful only when pasting msword content in firefox.
        2) the styles are still too much and it bloats up the html.
        3) It's not perfect and requires some more work, but it doesn't matter now!*/
        string SubStituteClassNameWithInlineCss(string element)
        {
            if (styleClasses == null) return element;

            int startIndex = element.IndexOf("class=");

            if (startIndex == -1) return element;
            int classNameStartIndex = startIndex + "class=".Length;

            int endIndex = element.IndexOf(" ", startIndex);
            if (endIndex == -1)
                endIndex = element.IndexOf(">", startIndex);
            if (endIndex == -1) return element;

            string className = GetDataBetweenQuotes(element.Substring(classNameStartIndex, endIndex - classNameStartIndex));
            foreach (KeyValuePair<string, string> kvp in styleClasses)
            {
                string value = kvp.Value;
                if (kvp.Key.IndexOf(string.Format(".{0}", className)) != -1)
                {
                    element = element.Remove(startIndex, endIndex - startIndex);
                    int styleStartIndex = element.IndexOf("style=");
                    if (styleStartIndex != -1)
                    {
                        int styleEndIndex = element.IndexOf(" ", styleStartIndex);
                        if (styleEndIndex == -1)
                            styleEndIndex = element.IndexOf(">", styleStartIndex);
                        if (styleEndIndex != -1)
                        {
                            string style = GetDataBetweenQuotes(element.Substring(styleStartIndex, styleEndIndex - styleStartIndex));
                            if (!style.EndsWith(";") && value.EndsWith(";"))
                                style += ";";
                            style += value;
                            element = element.Remove(styleStartIndex, styleEndIndex - styleStartIndex);

                            startIndex = styleStartIndex;
                        }
                    }
                    ErrorLog.WriteLine(string.Format("### style blocks invalid inside body element. Replacing classname '{0}' with inline css equivalent {1}", className, value));
                    element = element.Insert(startIndex, string.Format("style=\"{0}\"", value));
                    return element;
                }
            }
            return element;
        }

        bool SubstituteMatchingElement(string match, out string replacement)
        {
            foreach (KeyValuePair<string, string> kvp in ElementsToReplace)
            {
                if (match.IndexOf(kvp.Key, StringComparison.OrdinalIgnoreCase) != -1)
                {
                    if (match.IndexOf("<?xml", StringComparison.OrdinalIgnoreCase) == -1 &&
                        match.IndexOf("<meta", StringComparison.OrdinalIgnoreCase) == -1)
                        replacement = Regex.Replace(match, Regex.Escape(kvp.Key), kvp.Value,
                            RegexOptions.IgnoreCase);
                    else
                        replacement = Regex.Replace(match, Regex.Escape(match), kvp.Value,
                            RegexOptions.IgnoreCase);
                    return true;
                }
            }
            replacement = match;
            return false;
        }

        string UnitParse(string value)
        {
            string returnValue = value;
            if (!Helper.HasUnitDefined(value) && value.Length > 0)
                returnValue = value + "px";
            return returnValue;
        }

        #endregion Methods
    }
}